home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / cmds / fsattach / misc.c < prev    next >
Encoding:
C/C++ Source or Header  |  1991-01-13  |  17.0 KB  |  657 lines

  1. /* 
  2.  * misc.c --
  3.  *
  4.  *    Miscellaneous utility procedures for fsattach.
  5.  *
  6.  * Copyright 1989 Regents of the University of California
  7.  * Permission to use, copy, modify, and distribute this
  8.  * software and its documentation for any purpose and without
  9.  * fee is hereby granted, provided that the above copyright
  10.  * notice appear in all copies.  The University of California
  11.  * makes no representations about the suitability of this
  12.  * software for any purpose.  It is provided "as is" without
  13.  * express or implied warranty.
  14.  */
  15.  
  16. #ifndef lint
  17. static char rcsid[] = "$Header: /sprite/src/admin/fsattach/RCS/misc.c,v 1.10 91/01/12 16:55:15 jhh Exp $ SPRITE (Berkeley)";
  18. #endif /* not lint */
  19.  
  20. #include "fsattach.h"
  21.  
  22.  
  23. /*
  24.  *----------------------------------------------------------------------
  25.  *
  26.  * ParseMount --
  27.  *
  28.  *    Parses the mount information in the file and fills in the mount
  29.  *    table. 
  30.  *
  31.  * Results:
  32.  *    SUCCESS if the information was parsed ok, FAILURE otherwise.
  33.  *
  34.  * Side effects:
  35.  *    *countPtr contains the size of the mount table.
  36.  *
  37.  *
  38.  *----------------------------------------------------------------------
  39.  */
  40.  
  41. ReturnStatus
  42. ParseMount(mountFile, countPtr)
  43.     char    *mountFile;        /* file containing mount info */
  44.     int        *countPtr;        /* Ptr to size of mount table */
  45. {
  46.     static char     source[MAX_FIELD_LENGTH];
  47.     static char     string[MAX_LINE_LENGTH];
  48.     static char     dest[MAX_FIELD_LENGTH];
  49.     static char     command[MAX_FIELD_LENGTH];
  50.     static char     readFlag[MAX_FIELD_LENGTH];
  51.     static char     arg[MAX_FIELD_LENGTH];
  52.     static char     group[MAX_FIELD_LENGTH];
  53.     char         exportString[MAX_FIELD_LENGTH];
  54.     int         n;
  55.     Boolean        export;
  56.     char         *eof;
  57.     int         line;
  58.     int            index;
  59.     Boolean        readonly;
  60.     FILE        *stream;
  61.     ArgInfo        *argTable = NULL;
  62.     int            argTableSize;
  63.     int            argTableSizeIncrement = 5;
  64.     int            argCount;
  65.     ArgInfo        *argInfoPtr = NULL;
  66.     int            argIndex;
  67.     int            i;
  68.     int            j;
  69.     ReturnStatus    status;
  70.     ArgHeader        *argHeader;
  71.  
  72.     status = SUCCESS;
  73.     stream = fopen(mountFile, "r");
  74.     if (stream == (FILE *)NULL) {
  75.     (void) fprintf(stderr, "%s: can't open \"%s\", ", progName, mountFile);
  76.     perror("");
  77.     return FAILURE;
  78.     }
  79.     line = 0;
  80.     index = 0;
  81.     argCount = 0;
  82.     argTableSize = 0;
  83.     if (verbose) {
  84.     fprintf(stderr,"Parsing mount file %s.\n", mountFile);
  85.     }
  86.     for (eof = fgets(string, MAX_LINE_LENGTH, stream);
  87.      eof != NULL;
  88.      eof = fgets(string, MAX_LINE_LENGTH, stream)) {
  89.  
  90.     line++;
  91.     n = sscanf(string, " %256s", command);
  92.     if (n < 1 || *command == '#') {
  93.         continue;
  94.     }
  95.     if (index >= mountTableSize) {
  96.         mountTableSize += mountTableSizeIncrement;
  97.         mountTable = (MountInfo *) realloc(mountTable, mountTableSize);
  98.         if (mountTable == NULL) {
  99.         (void) fprintf(stderr,"%s: Out of memory.\n");
  100.         (void) exit(HARDERROR);
  101.         }
  102.     }
  103.     if (!strcasecmp("Attach", command)) {
  104.         n = sscanf(string, " %*s %256s %256s %256s %256s %256s", dest, 
  105.                source, group, exportString, readFlag);
  106.         if (n != 5) {
  107.         (void) fprintf(stderr, 
  108.                    "Garbled input at line %d of %s: \"%s\"\n", 
  109.                    line, mountFile, string);
  110.         continue;
  111.         }
  112.         export = TRUE;
  113.         if (!strcasecmp("export", exportString)) {
  114.         export = TRUE;
  115.         } else if (!strcasecmp("local", exportString)) {
  116.         export = FALSE;
  117.         } else {
  118.         (void) fprintf(stderr, "Bad export value at line %d: \"%s\"\n", 
  119.                    line, exportString);
  120.         continue;
  121.         }
  122.         for (i = 0; i < numGroups; i++) {
  123.         if (!strcmp(groupInfo[i].name, group)) {
  124.             break;
  125.         }
  126.         }
  127.         if (i == numGroups) {
  128.         if (i >= groupInfoSize) {
  129.             groupInfoSize += groupInfoSizeIncrement;
  130.             groupInfo = (GroupInfo *) realloc(groupInfo, groupInfoSize);
  131.             if (groupInfoSize == NULL) {
  132.             (void) fprintf(stderr,"%s: Out of memory.\n");
  133.             (void) exit(HARDERROR);
  134.             }
  135.         }
  136.         numGroups++;
  137.         strcpy(groupInfo[i].name, group);
  138.         }
  139.         mountTable[index].group = i;
  140.         if (!strcasecmp("r", readFlag)) {
  141.         readonly = TRUE;
  142.         } else  if (!strcasecmp("rw", readFlag)) {
  143.         readonly = FALSE;
  144.         } else {
  145.         (void) fprintf(stderr, 
  146.                    "Bad read/write value at line %d: \"%s\"\n",
  147.                    line, readFlag);
  148.         continue;
  149.         }
  150.         if (verbose) {
  151.         (void) printf("%-20s %-10s %-10s %-10s %-2s\n", dest, source, 
  152.                       group, exportString, readFlag);
  153.         }
  154.         mountTable[index].export = export;
  155.         mountTable[index].readonly = readonly;
  156.         mountTable[index].device = TRUE;
  157.         mountTable[index].doCheck = TRUE;
  158.         mountTable[index].checked = FALSE;
  159.         (void) strcpy(mountTable[index].source, source);
  160.         (void) strcpy(mountTable[index].dest, dest);
  161.         List_Init(&mountTable[index].argInfo.argList);
  162.         index++;
  163.     } else if (!strcasecmp("Export", command)) {
  164.         n = sscanf(string, " %*s %256s %256s", dest, source);
  165.         if (n != 2) {
  166.         (void) fprintf(stderr, 
  167.                    "Garbled input at line %d of %s: \"%s\"\n", 
  168.                    line, mountFile, string);
  169.         continue;
  170.         }
  171.         if (verbose) {
  172.         printf("%-20s %-10s\n", dest, source);
  173.         }
  174.         mountTable[index].export = TRUE;
  175.         mountTable[index].readonly = FALSE;
  176.         mountTable[index].device = FALSE;
  177.         mountTable[index].doCheck = FALSE;
  178.         mountTable[index].checked = FALSE;
  179.         (void) strcpy(mountTable[index].source, source);
  180.         (void) strcpy(mountTable[index].dest, dest);
  181.         List_Init(&mountTable[index].argInfo.argList);
  182.         index++;
  183.     } else {
  184.         n = sscanf(string, " %256s", source);
  185.         if (n != 1) {
  186.         (void) fprintf(stderr, 
  187.                    "Garbled input at line %d of %s: \"%s\"\n", 
  188.                    line, mountFile, string);
  189.         continue;
  190.         }
  191.         if (argCount >= argTableSize) {
  192.         argTableSize += argTableSizeIncrement;
  193.         if (argCount == 0) {
  194.             Alloc(argTable, ArgInfo, argTableSize, "argTable");
  195.         } else {
  196.             argTable = (ArgInfo *) realloc(argTable, argTableSize);
  197.         }
  198.         if (argTable == NULL) {
  199.             (void) fprintf(stderr,"%s: Out of memory.\n");
  200.             (void) exit(HARDERROR);
  201.         }
  202.         }
  203.         i = 0;
  204.         Alloc(argInfoPtr, ArgInfo, 1, "argInfoPtr");
  205.         List_Init(&argInfoPtr->argList);
  206.         argInfoPtr->line = line;
  207.         strcpy(argInfoPtr->source, source);
  208.         for (argIndex = strspn(string, " \t"); 
  209.          string[argIndex] != '\0';
  210.          argIndex += strcspn(&string[argIndex], " \t\n"),
  211.          argIndex += strspn(&string[argIndex], " \t\n"), 
  212.          i++) {
  213.  
  214.          if (i < 1) {
  215.              continue;
  216.          }
  217.          if (sscanf(&string[argIndex], " %s", arg) == 0) {
  218.              break;
  219.          }
  220.          Alloc(argHeader, ArgHeader, 1, "argHeader");
  221.          List_InitElement((List_Links *) argHeader);
  222.          Alloc(argHeader->arg, char, strlen(arg) + 1, "arg");
  223.          strcpy(argHeader->arg, arg);
  224.          List_Insert((List_Links *) argHeader, 
  225.              LIST_BEFORE((List_Links *) &argInfoPtr->argList));
  226.          }
  227.         if (strcasecmp(source, "all")) {
  228.         for (i = 0; i < index; i++) {
  229.              if (!strcmp(mountTable[i].source, source)) {
  230.              MergeList(&argInfoPtr->argList, 
  231.                  &mountTable[i].argInfo.argList);
  232.              break;
  233.              }
  234.          }
  235.          if (i == index) {
  236.              argTable[argCount] = *argInfoPtr;
  237.              List_Init(&argTable[argCount].argList);
  238.              AddList(&argInfoPtr->argList, 
  239.              &argTable[argCount].argList);
  240.              free(argInfoPtr);
  241.              argCount++;
  242.          }
  243.          } else {
  244.          argTable[argCount] = *argInfoPtr;
  245.          List_Init(&argTable[argCount].argList);
  246.          AddList(&argInfoPtr->argList, 
  247.              &argTable[argCount].argList);
  248.          free(argInfoPtr);
  249.          argCount++;
  250.          }
  251.     }   
  252.     }
  253.     for (i = 0; i < argCount; i++) {
  254.     if (!strcasecmp(argTable[i].source, "all")) {
  255.         for (j = 0; j < index; j++) {
  256.         if (mountTable[j].device == TRUE) {
  257.             MergeList(&argTable[i].argList, 
  258.             &mountTable[j].argInfo.argList);
  259.             strcpy(mountTable[j].argInfo.source, argTable[i].source);
  260.             mountTable[j].argInfo.line = argTable[i].line;
  261.         }
  262.         }
  263.         DeleteList(&argTable[i].argList);
  264.     } else {
  265.         for (j = 0; j < index; j++) {
  266.          if (!strcmp(argTable[i].source, mountTable[j].source)) {
  267.              MergeList(&argTable[i].argList, 
  268.              &mountTable[j].argInfo.argList);
  269.             strcpy(mountTable[i].argInfo.source, 
  270.                 argTable[i].source);
  271.             mountTable[i].argInfo.line = argTable[i].line;
  272.              break;
  273.          }
  274.          }
  275.          if (j == index) {
  276.          fprintf(stderr, "Device %s not found at line %d\n", 
  277.              argTable[i].source, argTable[i].line);
  278.          status = FAILURE;
  279.          }
  280.      }
  281.      }
  282.     if (argTable != NULL) {
  283.     free(argTable);
  284.     }
  285.     fclose(stream);
  286.     *countPtr = index;
  287.     return status;
  288. }
  289.  
  290. /*
  291.  *----------------------------------------------------------------------
  292.  *
  293.  * MergeList --
  294.  *
  295.  *    Merge the src list into the dest list without modifying the source
  296.  *    list.
  297.  *
  298.  * Results:
  299.  *    None.
  300.  *
  301.  * Side effects:
  302.  *    Memory is allocated and the dest list is modified.
  303.  *
  304.  *----------------------------------------------------------------------
  305.  */
  306.  
  307. void
  308. MergeList(srcListPtr, destListPtr)
  309.     ArgHeader *srcListPtr;        /* source list */
  310.     ArgHeader *destListPtr;        /* destination list */
  311.  
  312. {
  313.     List_Links    *itemPtr;
  314.     List_Links    *newPtr;
  315.  
  316.     LIST_FORALL((List_Links *) srcListPtr, itemPtr) {
  317.     Alloc((ArgHeader *) newPtr, ArgHeader, 1, "newPtr");
  318.     *((ArgHeader *) newPtr) = *((ArgHeader *) itemPtr);
  319.     List_Insert(newPtr, LIST_ATREAR((List_Links *) destListPtr));
  320.     }
  321. }
  322.  
  323. /*
  324.  *----------------------------------------------------------------------
  325.  *
  326.  * AddList --
  327.  *
  328.  *    Adds the src list to the dest list. 
  329.  *
  330.  * Results:
  331.  *    None.
  332.  *
  333.  * Side effects:
  334.  *    The dest list is modified. 
  335.  *
  336.  *----------------------------------------------------------------------
  337.  */
  338.  
  339. void
  340. AddList(srcListPtr, destListPtr)
  341.     ArgHeader *srcListPtr;        /* source list */
  342.     ArgHeader *destListPtr;        /* destination list */
  343.  
  344. {
  345.     List_Links    *itemPtr;
  346.     List_Links    *tempPtr;
  347.  
  348.     itemPtr = List_First((List_Links *) srcListPtr);
  349.     while (!List_IsAtEnd((List_Links *) srcListPtr, itemPtr)) {
  350.     tempPtr = itemPtr;
  351.     itemPtr = List_Next(itemPtr);
  352.     List_Remove(tempPtr);
  353.     List_Insert(tempPtr, LIST_ATREAR((List_Links *) destListPtr));
  354.     }
  355. }
  356.  
  357. /*
  358.  *----------------------------------------------------------------------
  359.  *
  360.  * DeleteList --
  361.  *
  362.  *    Frees a list.
  363.  *
  364.  * Results:
  365.  *    None.
  366.  *
  367.  * Side effects:
  368.  *    The list is freed.
  369.  *
  370.  *----------------------------------------------------------------------
  371.  */
  372.  
  373. void
  374. DeleteList(listPtr)
  375.     ArgHeader    *listPtr;    /* list to be freed */
  376. {
  377.     List_Links    *itemPtr;
  378.     List_Links    *tempPtr;
  379.  
  380.     itemPtr = List_First((List_Links *) listPtr);
  381.     while (!List_IsAtEnd((List_Links *) listPtr, itemPtr)) {
  382.     tempPtr = itemPtr;
  383.     itemPtr = List_Next(itemPtr);
  384.     List_Remove(tempPtr);
  385.     free(tempPtr);
  386.     }
  387. }
  388.  
  389.  
  390. /*
  391.  *----------------------------------------------------------------------
  392.  *
  393.  * MoveOutput --
  394.  *
  395.  *    Fscheck stores its output in a special
  396.  *    preallocated file. We want to copy the information out of that
  397.  *    file into the standard fscheck output file, and then zero out
  398.  *    the special file.
  399.  *
  400.  * Results:
  401.  *    None.
  402.  *
  403.  * Side effects:
  404.  *    The root output is appended to the output file and the special
  405.  *    output file is filled with null characters.
  406.  *
  407.  *----------------------------------------------------------------------
  408.  */
  409.  
  410. void
  411. MoveOutput(mountCount)
  412.     int        mountCount;
  413. {
  414.     FILE     *outputStream;
  415.     FILE    *tempStream;
  416.     char    outputFile[MAX_LINE_LENGTH];
  417.     char    inputFile[MAX_LINE_LENGTH];
  418.     int        bytesRead;
  419.     int        bytesWritten;
  420.     int        bytesToWrite;
  421.     char    buffer[1024];
  422.     int        i, mountIndex;
  423.     Boolean    done;
  424.     char    *hostName;
  425.  
  426.     if (verbose) {
  427.     printf("Moving output from fscheck.\n");
  428.     }
  429.     hostName = getenv("HOST");
  430.     for(mountIndex = 0; mountIndex < mountCount; mountIndex++) {
  431.     if (debug) {
  432.         printf("%d (%s): device = %s, status = %s\n", mountIndex, 
  433.         mountTable[mountIndex].source,
  434.         (mountTable[mountIndex].device == TRUE ? "true" : "false"),
  435.         (mountTable[mountIndex].status == CHILD_OK) ? "ok" : "not ok");
  436.     }
  437.     if (mountTable[mountIndex].checked == FALSE ||
  438.         mountTable[mountIndex].status != CHILD_OK ||
  439.         mountTable[mountIndex].device == FALSE) {
  440.         continue;
  441.     }
  442.     (void) sprintf(outputFile, "/hosts/%s/%s.fsc", hostName,
  443.         mountTable[mountIndex].source);
  444.     if (verbose) {
  445.         printf("Copying output from checking %s to %s.\n", 
  446.         mountTable[mountIndex].source, outputFile);
  447.     }
  448.     outputStream = fopen(outputFile, "a+");
  449.     if (outputStream == (FILE *)NULL) {
  450.         (void) fprintf(stderr, "%s: can't open \"%s\", ", progName, 
  451.             outputFile);
  452.         perror("");
  453.         return;
  454.     }
  455.     (void) sprintf(inputFile, "%s/%s", mountTable[mountIndex].dest,
  456.         tempOutputFile);
  457.     tempStream = fopen(inputFile,"r+");
  458.     if (tempStream == (FILE *)NULL) {
  459.         (void) fprintf(stderr, "%s: can't open \"%s\", ", progName,
  460.                inputFile);
  461.         perror("");
  462.         fclose(outputStream);
  463.         return;
  464.     }
  465.     bytesRead = fread(buffer, sizeof(char), 1024, tempStream);
  466.     done = FALSE;
  467.     while(bytesRead > 0 && !done) {
  468.         for (i = 0; i < bytesRead; i++) {
  469.         if ( buffer[i] == '\0') {
  470.             done = TRUE;
  471.             break;
  472.         }
  473.         }
  474.         bytesToWrite = i;
  475.         bytesWritten = fwrite(buffer, sizeof(char), bytesToWrite, 
  476.         outputStream);
  477.         if (bytesWritten < bytesToWrite) {
  478.         (void) fprintf(stderr, "%s: Unable to copy output to %s.\n",
  479.             progName, outputFile);
  480.         goto cleanup;
  481.         }
  482.         bytesRead = fread(buffer, sizeof(char), 1024, tempStream);
  483.     }
  484.     rewind(tempStream);
  485.     (void) bzero(buffer, 1024);
  486.     for (i = 0; i < tempOutputFileSize; i += bytesToWrite) {
  487.         bytesToWrite = min(1024, tempOutputFileSize - i);
  488.         bytesWritten = fwrite(buffer, sizeof(char), bytesToWrite, 
  489.                     tempStream);
  490.         if (bytesWritten != bytesToWrite) {
  491.         fprintf(stderr, "Only wrote %d bytes: ", bytesWritten);
  492.         perror("");
  493.         break;
  494.         }
  495.     }
  496. cleanup:
  497.     (void) fclose(outputStream);
  498.     (void) fclose(tempStream);
  499.     }
  500. }
  501.  
  502. /*
  503.  *----------------------------------------------------------------------
  504.  *
  505.  * PrintFscheckError --
  506.  *
  507.  *    Prints a meaningful description of fscheck's error codes.
  508.  *
  509.  * Results:
  510.  *    None.
  511.  *
  512.  * Side effects:
  513.  *    None.
  514.  *
  515.  *----------------------------------------------------------------------
  516.  */
  517.  
  518. void
  519. PrintFscheckError(code, mountPtr)
  520.     char    code;        /* error code from fscheck */
  521.     MountInfo    *mountPtr;    /* mount info for partition checked */
  522. {
  523.     static char *softerrors[] = {
  524.     "No errors",
  525.     "Correctable error",
  526.     "Exceeded heap limit",
  527.     "No reboot",
  528.     "Reboot",
  529.     };
  530.     static char *harderrors[] = {
  531.     "",
  532.     "Read of device failed",
  533.     "Write to device failed",
  534.     "Bad argument",
  535.     "Heap limit too small",
  536.     "Disk is full",
  537.     };
  538.     char *error;
  539.     int     index;
  540.  
  541.     index = (int) code;
  542.     if (index < 0) {
  543.     error = harderrors[-index];
  544.     } else { 
  545.     error = softerrors[index];
  546.     }
  547.     (void) fprintf(stderr, "Fscheck of %s returned (%d) : %s.\n", 
  548.            mountPtr->source, index, error);
  549. }
  550.  
  551. /*
  552.  *----------------------------------------------------------------------
  553.  *
  554.  * PreloadPrefixTable --
  555.  *
  556.  *    Load the prefix table with ourself as the server of all the
  557.  *     prefixes we export. That way we don't broadcast for them
  558.  *    while we check our disks.
  559.  *
  560.  * Results:
  561.  *    None.
  562.  *
  563.  * Side effects:
  564.  *    Exported prefixes are loaded into the prefix table.
  565.  *
  566.  *----------------------------------------------------------------------
  567.  */
  568.  
  569. void
  570. PreloadPrefixTable(spriteID, mountCount)
  571.     int    spriteID;        /* Our Sprite ID. */
  572.     int    mountCount;        /* Size of mount table. */
  573. {
  574.     int            i;
  575.     Fs_PrefixLoadInfo    loadInfo;
  576.     ReturnStatus    status;
  577.  
  578.     for (i = 0; i < mountCount; i++) {
  579.     if ((mountTable[i].export == TRUE) && 
  580.         (strcmp("/", mountTable[i].dest) != 0)) {
  581.         (void) strncpy(loadInfo.prefix, mountTable[i].dest,
  582.             FS_MAX_PATH_NAME_LENGTH);
  583.         loadInfo.serverID = spriteID;
  584.         if (printOnly || verbose) {
  585.         printf("Preloading prefix \"%s\"\n", loadInfo.prefix);
  586.         }
  587.         if (!printOnly) {
  588.         status = Fs_Command(FS_PREFIX_LOAD, sizeof(Fs_PrefixLoadInfo), 
  589.              &loadInfo);
  590.         if (status != SUCCESS) {
  591.             fprintf(stderr, "Couldn't load prefix \"%s\": %s.\n",
  592.                 mountTable[i].dest, Stat_GetMsg(status));
  593.         }
  594.         }
  595.     }
  596.     }
  597. }
  598.  
  599. /*
  600.  *----------------------------------------------------------------------
  601.  *
  602.  * GetAttachName --
  603.  *
  604.  *    Get the name of the prefix under which the filesystem was
  605.  *    attached.
  606.  *
  607.  * Results:
  608.  *    Name of the prefix.
  609.  *
  610.  * Side effects:
  611.  *    The disk is read.
  612.  *
  613.  *----------------------------------------------------------------------
  614.  */
  615.  
  616. char *
  617. GetAttachName(device)
  618.     char    *device;
  619. {
  620.     int            fd;
  621.     Disk_Label        *labelPtr;
  622.     Ofs_SummaryInfo    *summaryPtr;
  623.     char        *prefix;
  624.     ReturnStatus    status;
  625.  
  626.     fd = open(device, O_RDWR);
  627.     if (fd < 0) {
  628.     fprintf(stderr, "Could not open %s\n", device);
  629.     return NULL;
  630.     }
  631.     labelPtr = Disk_ReadLabel(fd);
  632.     if (labelPtr == NULL) {
  633.     fprintf(stderr, "Could not read label from %s\n", device);
  634.     return NULL;
  635.     }
  636.     summaryPtr = Disk_ReadSummaryInfo(fd, labelPtr);
  637.     if ( summaryPtr == NULL) {
  638.     fprintf(stderr, "Could not read summary info from %s\n", device);
  639.     return NULL;
  640.     }
  641.     prefix = summaryPtr->domainPrefix;
  642.     /*
  643.      * FIX THIS:  The following code to clear a bit in the summary sector
  644.      * is only needed for kernels prior to 1.070.  It can be removed
  645.      * once those kernels are gone.  JHH.
  646.      */
  647.     summaryPtr->flags &= ~OFS_DOMAIN_JUST_CHECKED;
  648.     status = Disk_WriteSummaryInfo(fd, labelPtr, summaryPtr);
  649.     if (status != SUCCESS) {
  650.     fprintf(stderr, "Could not write summary info to %s\n", device);
  651.     return NULL;
  652.     }
  653.     close(fd);
  654.     return prefix;
  655. }
  656.  
  657.